695. Range Variation Query
Последовательность
an задается следующей
формулой:
an = n2 mod 12345 + n3 mod 23456
Необходимо многократно
отвечать на запросы двух типов:
·
найти разность между максимальным и минимальным значениями среди элементов ai, ai + 1, ..., aj;
·
присвоить элементу ai
новое значение j.
Вход. Первая строка содержит одно натуральное число k (k
≤ 105) – количество запросов. Каждая из следующих k строк описывает один запрос двумя целыми числами xi и yi:
Если xi
> 0, необходимо
найти разность между максимальным и минимальным значениями на отрезке axi ... ayi (1
≤ xi ≤ yi ≤ 105).
Если xi
< 0, необходимо
присвоить элементу a-xi (-105
≤ xi ≤ -1) значение
yi (|yi| ≤ 105).
Выход. Для каждого запроса
первого типа выведите в отдельной строке разность между максимальным и
минимальным значениями на указанном отрезке.
Пример
входа |
Пример
выхода |
7 1 3 2 4 -2 -100 1 5 8 9 -3 -101 2 3 |
34 68 250 234 1 |
структуры данных – дерево отрезков
Поскольку задача включает
модификацию элементов, её можно рассматривать как задачу динамического RMQ
(Range Minimum Query) и решать с использованием дерева отрезков. Для этого
необходимо реализовать следующие операции:
·
создание дерева отрезков по массиву а;
·
нахождение минимума и максимума на заданном отрезке;
·
единичную модификацию элемента.
Пример
Пусть a0 = 0. Сгенерируем
первые 10 элементов последовательности ai:
Выполним
запросы, приведенные в примере:
1 3 |
max[1..3] – min[1..3] = 36
– 2 = 34 |
2 4 |
max[2..4] – min[2..4] = 80
– 12 = 68 |
-2 -100 |
a2 = -100 |
1 5 |
max[1..5] – min[1..5] = 150
– (-100) = 250 |
8 9 |
max[8..9] – min[8..9] = 810
– 576 = 234 |
-3 -101 |
a3 = -101 |
2 3 |
max[2..3] – min[2..3] =
-100 – (-101) = 1 |
Реализация алгоритма
Дерево отрезков
храним в массиве SegTree длины 4*MAX, где MAX –
максимальное количество элементов, которое может храниться в отрезке. Структура
node хранит минимальное min и максимальное max значение на отрезке.
#define MAX 100000
struct node
{
int min, max;
} SegmentTree[4*MAX];
На вход
процедуре build построения дерева отрезков передается массив a, номер текущей
вершины дерева Vertex и границы
отрезка Left и Right, соответствующие вершине Vertex.
void BuildTree(int
*a, int Vertex, int
Left, int Right)
{
if (Left ==
Right)
SegmentTree[Vertex].min =
SegmentTree[Vertex].max = a[Left];
else
{
int Middle
= (Left + Right) / 2;
BuildTree(a, 2*Vertex, Left, Middle);
BuildTree(a, 2*Vertex+1, Middle+1, Right);
Пересчитываем минимальное и
максимальное значение на отрезке [Left, Right], используя информацию сыновей
вершины Vertex.
SegmentTree[Vertex].min =
min(SegmentTree[2*Vertex].min,SegmentTree[2*Vertex+1].min);
SegmentTree[Vertex].max =
max(SegmentTree[2*Vertex].max,SegmentTree[2*Vertex+1].max);
}
}
Вершине Vertex соответствует отрезок [LeftPos; RightPos]. Функция GetMin возвращает минимум на отрезке [Left; Right].
int GetMin(int
Vertex, int LeftPos, int
RightPos, int Left, int
Right)
{
if (Left >
Right) return INF;
if ((Left ==
LeftPos) && (Right == RightPos))
return
SegmentTree[Vertex].min;
int Middle =
(LeftPos + RightPos) / 2;
return
min(GetMin(2*Vertex, LeftPos, Middle, Left, min(Right,Middle)),
GetMin(2*Vertex+1, Middle+1, RightPos,
max(Left,Middle+1),Right));
}
Вершине Vertex соответствует
отрезок [LeftPos; RightPos]. Функция GetMax возвращает
максимум на отрезке [Left; Right].
int GetMax(int
Vertex, int LeftPos, int
RightPos, int Left, int
Right)
{
if (Left >
Right) return -INF;
if ((Left ==
LeftPos) && (Right == RightPos))
return
SegmentTree[Vertex].max;
int Middle =
(LeftPos + RightPos) / 2;
return
max(GetMax(2*Vertex, LeftPos, Middle, Left, min(Right,Middle)),
GetMax(2*Vertex+1, Middle+1,
RightPos, max(Left,Middle+1),Right));
}
Вершине Vertex соответствует
отрезок [LeftPos; RightPos]. Функция Update присваивает
элементу с индексом Position значение
NewValue.
void Update(int
Vertex, int LeftPos, int
RightPos,
int
Position, int NewValue)
{
Если вершине Vertex
соответствует отрезок, состоящий из одного элемента (LeftPos равно RightPos),
то мы дошли до листа дерева отрезков. Минимум и максимум на отрезке, состоящего
из одного элемента, равен NewValue.
if (LeftPos == RightPos)
SegmentTree[Vertex].min =
SegmentTree[Vertex].max = NewValue;
else
{
Иначе вычисляем, в каком – левом или
правом сыне вершины Vertex лежит элемент с индексом Position. Запускаем рекурсивно его модификацию.
int Middle
= (LeftPos + RightPos) / 2;
if
(Position <= Middle)
Update (2*Vertex, LeftPos, Middle,
Position, NewValue);
else
Update (2*Vertex+1, Middle+1, RightPos,
Position, NewValue);
Пересчитываем значение минимума min
и максимума max в текущей вершине Vertex дерева отрезков.
SegmentTree[Vertex].min =
min(SegmentTree[2*Vertex].min,SegmentTree[2*Vertex+1].min);
SegmentTree[Vertex].max =
max(SegmentTree[2*Vertex].max,SegmentTree[2*Vertex+1].max);
}
}
Основная часть программы. Генерируем
последовательность ai
согласно приведенной в условии задаче формуле. Последовательность храним в
массиве int a[MAX+1];
for(i = 0; i
<= MAX; i++)
a[i] = (i * i) % 12345 + ((i * i) % 23456 *
i) % 23456;
Строим дерево отрезков.
BuildTree(a,1,0,MAX);
Читаем входные данные и отвечаем на
запросы.
scanf("%d",&k);
while(k--)
{
scanf("%d
%d",&x,&y);
if (x > 0)
printf("%d\n",GetMax(1,0,MAX,x,y) -
GetMin(1,0,MAX,x,y));
else Update(1,0,MAX,-x,y);
}
Вторая реализация при помощи
массива
Обозначим через INF плюс бесконечность.
#define INF 2000000000
Дерево отрезков будем хранить в виде
кучи в ячейках v[1]..v[2*n], всего 2
* n – 1 элементов. Сыновьями вершины v[i] будут v[2*i] и v[2*i + 1]. Функция BuildTree
строит дерево отрезков из элементов вектора a. Поскольку в каждой вершине
дерева требуется поддерживать минимум и максимум на отрезке, то элементом v[i] будет пара
целых чисел. Первое число пары соответствует минимуму, а второе максимуму на
отрезке.
void BuildTree(vector<pair<int,int> >
&a)
{
Увеличим размер массива n до степени двойки, введя фиктивные
вершины.
int i, n = (1
<< (int)(log(1.0*(a.size() -
1))/log(2.0)) + 1);
a.resize(2*n, make_pair(INF,0));
for(i = n ; i
< 2 * n; i++) a[i] = a[i - n];
for (i = n -
1; i > 0; i--)
a[i].first
= min(a[2 * i].first, a[2 * i + 1].first),
a[i].second = max(a[2 * i].second, a[2 * i
+ 1].second);
}
Функция MinMax вычисляет максимум и минимум на отрезке.
Возвращает их разность.
int MinMax(vector<pair<int,int>
>&v, int l, int
r)
{
int minRes =
INF, maxRes = -INF;
int n =
v.size() / 2;
l += n - 1, r += n - 1;
while (l
<= r)
{
Если l – правый сын своего родителя, то учитываем его фундаментальный
отрезок.
if (l &
1)
minRes = min(minRes, v[l].first),
maxRes = max(maxRes, v[l].second);
Если r – левый сын своего родителя, то учитываем его фундаментальный
отрезок.
if (!(r
& 1))
minRes = min(minRes, v[r].first),
maxRes = max(maxRes, v[r].second);
Сдвигаем указатели на уровень выше.
l = (l + 1) / 2, r = (r - 1) / 2;
}
return maxRes
- minRes;
}
Присвоим i-ому
элементу массива значение x.
void update(vector<pair<int,int>
>&v, int i, int
x)
{
int n =
v.size() / 2;
i += n - 1;
Присваиваем значение x листу.
v[i] = make_pair(x,x);
Двигаемся от листа к корню,
пересчитываем значения минимумов и максимумов. Отцом вершины i является вершина .
while (i /=
2)
v[i].first
= min(v[2 * i].first, v[2 * i + 1].first),
v[i].second = max(v[2 * i].second, v[2 * i
+ 1].second);
}
Основная часть программы. Генерируем последовательность ai согласно приведенной в
условии задаче формуле и заносим ее в вектор v.
vector<pair<int,int> > v;
for(i = 1; i <= 100000; i++)
{
ai = (i * i) % 12345 + ((i * i) % 23456 * i)
% 23456;
v.push_back(make_pair(ai,ai));
}
Строим дерево отрезков.
BuildTree(v);
Читаем входные данные и отвечаем на запросы.
scanf("%d",&k);
while(k--)
{
scanf("%d
%d",&x,&y);
if (x > 0)
printf("%d\n",MinMax(v,x,y));
else
update(v,-x,y);
}